/***************************************************************
*  Copyright (c) 2007 by GroupMe! Team (www.groupme.net)
*  All rights reserved.
*
*  This file is part of the GroupMe! Project. Source code of 
*  this project is closed and redistribution of this code is
*  prohibited. 
*  
*  Contact: http://www.groupme.net
*
*  This copyright notice MUST APPEAR in all copies of the file!
***************************************************************/
package net.groupme.search.strategy.folkrank;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import cern.colt.matrix.DoubleMatrix1D;
import cern.colt.matrix.impl.DenseDoubleMatrix1D;
import cern.colt.matrix.impl.SparseDoubleMatrix2D;

import net.groupme.model.ObjectFactory;
import net.groupme.search.strategy.model.PreferencesInTags;
import net.groupme.search.strategy.model.TAS;
import net.groupme.search.strategy.model.TASImpl;


/**
 * created on 16 Jan 2008
 * 
 * This class implements a strategy where Tags are constructed via id of a tag and id of 
 * a group, in which the tag was assigned, afterwards normal FolkRank
 * <pre>
 * 1. Construct Adjacency matrix A only with 'Artificial Tags':
 * A = u1 u2 .. r1 .. t1g1 t2g1 ...
 * 
 * 2. Compute FolkRank.
 * 3. return rankings for users, tags, resources
 * 
 * Idea: 
 * Hypothesis: 
 * sim((r1,t,u,g1), (r2,t,u,g1))
 *  >
 * sim((r1,t,u,g1), (r3,t,u,g2))
 * </pre>
 * @author Fabian Abel, <a href="mailto:abel@l3s.de">abel@l3s.de</a>
 * @author last edited by: $Author: fabian $
 * 
 * @version $Revision: 1.2 $ $Date: 2008-01-26 20:00:59 $
 */
public class FolkRankWithTxGyTags extends FolkRankWithGroupsAsArtificialTags {
	
	/** artifcial tags are constructed via the id of a tag and this suffix + id of a group and {@link FolkRankWithGroupsAsArtificialTags#ARTIFICIAL_GROUP_TAG_SUFFIX} */
	public static final String ARTIFICIAL_TAG_SUFFIX = "t";
	
	/** suffix used when a group was not tagged in context of another group */
	public static final String GROUP_NULL_INDICATOR = "null";

	/**
	 * Creates a new FolkRankWithTxGyTags instance. 
	 */
	public FolkRankWithTxGyTags() {
		super();
		artificialTagIdColumMapping = new HashMap<String, Integer>();
	}

	/* (non-Javadoc)
	 * @see net.groupme.search.strategy.folkrank.NormalFolkRankRankingStrategy#getName()
	 */
	@Override
	public String getName() {
		return "TxGy Tags";
	}

	/* (non-Javadoc)
	 * @see net.groupme.search.strategy.folkrank.NormalFolkRankRankingStrategy#getDescription()
	 */
	@Override
	public String getDescription() {
		return "Tags are constructed via id of a tag and id of a group, in which the tag was assigned, afterwards normal FolkRank";
	}
	
	/* (non-Javadoc)
	 * @see net.groupme.search.strategy.folkrank.NormalFolkRankRankingStrategy#computeFolkrankVector(net.groupme.search.strategy.model.PreferencesInTags)
	 */
	@Override
	protected DoubleMatrix1D computeFolkrankVector(PreferencesInTags prefs){
		if(adjacencyMatrix == null){
			constructAdjacencyMatrix();
		}
		DoubleMatrix1D preferenceVector = null;
		DoubleMatrix1D folkrankVector = null;
			
		//1. select personalization vector:
		preferenceVector = new DenseDoubleMatrix1D(adjacencyMatrix.rows());
		try {
			FolkRank.setPreferenceVektorForTxGyTags(preferenceVector, prefs, artificialTagIdColumMapping);
		} catch (SQLException e) {
			e.printStackTrace();
		}
			
		//2. compute FolkRank:
		folkrankVector = FolkRank.computeFolkrank(adjacencyMatrix, preferenceVector);
		return folkrankVector;
	}
	
	/* (non-Javadoc)
	 * @see net.groupme.search.strategy.folkrank.NormalFolkRankRankingStrategy#constructAdjacencyMatrix()
	 */
	@Override
	protected void constructAdjacencyMatrix(){
		/* 1. Gather data: */
		Statement sqlStatement = null;
		List<TAS> tagAssignments = null;
		
		try {
			sqlStatement = ObjectFactory.ds.getConnection().createStatement();
		
			//addNormalMappings() adds also the artificial tags a la "T4G1"
			tagAssignments = addNormalMappings(sqlStatement);
		
		
			/* 2. Compute the Matrix: */
			
			//construct Matrix
			int numberOfVertices = userIdColumMapping.size() + tagIdColumMapping.size() + resourceIdColumMapping.size() + artificialTagIdColumMapping.size();
			adjacencyMatrix = new SparseDoubleMatrix2D(numberOfVertices,numberOfVertices);
			
			//fill adjacency matrix
			fillMatrixWithNormalTagAssignments(tagAssignments);
			
			//row-wise 1-Norm:
			computeRowWiseThe1Norm();
		}catch (SQLException e) {
			e.printStackTrace();
		}
		
	}
	
	/* (non-Javadoc)
	 * @see net.groupme.search.strategy.folkrank.NormalFolkRankRankingStrategy#addNormalMappings(java.sql.Statement)
	 */
	@Override
	protected List<TAS> addNormalMappings(Statement sqlStatement) {
		ResultSet result = null;
		List<TAS> tas = new ArrayList<TAS>();
		try {			
			//get TAS and define mappings: 
			result = sqlStatement.executeQuery("Select userId, tagId, resourceId, groupId from userTagGroupResource  ");
			userIdColumMapping = new HashMap<Integer, Integer>();
			tagIdColumMapping = new HashMap<Integer, Integer>();
			resourceIdColumMapping = new HashMap<Integer, Integer>();
			artificialTagIdColumMapping = new HashMap<String, Integer>();
			TAS actualTas = null;
			position = 0;
			while(result.next()){
				actualTas = new TASImpl(result.getInt(1), result.getInt(2),result.getInt(3),result.getInt(4));
				tas.add(actualTas);	
				if(!userIdColumMapping.containsKey(actualTas.getUserId())){
					userIdColumMapping.put(actualTas.getUserId(), position);
					position++;
				}
				if(!resourceIdColumMapping.containsKey(actualTas.getResourceId())){
					resourceIdColumMapping.put(actualTas.getResourceId(), position);
					position++;
				}
			}
			//untagged Resources: 
			result = sqlStatement.executeQuery("SELECT id FROM Resource where id NOT in (Select distinct resourceId from userTagGroupResource)");
			while(result.next()){
				if(!resourceIdColumMapping.containsKey(result.getInt(1))){
					resourceIdColumMapping.put(result.getInt(1), position);
					position++;
				}
			}
			
			//create artificial tags:
			//TODO: We should also include untagged resources and tag them with null_gx
			result = sqlStatement.executeQuery("SELECT distinct CONCAT('" + ARTIFICIAL_TAG_SUFFIX + "', tagId, '" + ARTIFICIAL_GROUP_TAG_SUFFIX + "', groupId) FROM userTagGroupResource where groupId is NOT NULL");
			String artificialId = null;
			while(result.next()){
				artificialId = result.getString(1);
				artificialTagIdColumMapping.put(artificialId, position);
				position++;
			}
			result = sqlStatement.executeQuery("SELECT distinct CONCAT('" + ARTIFICIAL_TAG_SUFFIX + "', tagId, 'g" + GROUP_NULL_INDICATOR + "') FROM userTagGroupResource where groupId is NULL");
			while(result.next()){
				artificialId = result.getString(1);
				if(!artificialTagIdColumMapping.containsKey(artificialId)){
					artificialTagIdColumMapping.put(artificialId, position);
					position++;
				}
			}
		}catch (SQLException e) {
			e.printStackTrace();
			logger.info("Gathering tag assignments from DB failed.", e);
		}
		
		return tas;
	}
	
	
	/* (non-Javadoc)
	 * @see net.groupme.search.strategy.folkrank.NormalFolkRankRankingStrategy#fillMatrixWithNormalTagAssignments(java.util.List)
	 */
	@Override
	protected void fillMatrixWithNormalTagAssignments(List<TAS> tagAssignments) {
		double userTagScore = 0.0;
		double tagResourceScore = 0.0;
		double userResourceScore = 0.0;
		int userPos = 0, resourcePos = 0, artificialPos = 0;
		
		//construct Matrix
		int numberOfVertices = userIdColumMapping.size() + tagIdColumMapping.size() + resourceIdColumMapping.size() + artificialTagIdColumMapping.size();
		adjacencyMatrix = new SparseDoubleMatrix2D(numberOfVertices,numberOfVertices);
		
		//fill adjacency matrix
		String artificialTag = null;
		try {
			for (TAS tas : tagAssignments) {
				artificialTag = constructTxGyTag(tas.getTagId(), tas.getGroupId());
				
				userTagScore = FolkRank.computeUserTagWeightTXGY(tagAssignments, tas.getUserId(), artificialTag);
				tagResourceScore = FolkRank.computeTagResourceWeightTXGY(tagAssignments, tas.getResourceId(), artificialTag);
				userResourceScore = FolkRank.computeUserResourceWeight(tagAssignments, tas.getUserId(), tas.getResourceId());
				
				//get indices
				userPos = userIdColumMapping.get(tas.getUserId());
				resourcePos = resourceIdColumMapping.get(tas.getResourceId());
				try{
					artificialPos = artificialTagIdColumMapping.get(artificialTag);
				}catch(Exception e){
					e.printStackTrace();
					logger.info("Artificial TxGy Tag has no index within the adjacency matrix.", e);
				}
				//set userArtificialTag-Score
				adjacencyMatrix.set(userPos, artificialPos, userTagScore);
				adjacencyMatrix.set(artificialPos,userPos, userTagScore);
				
				//set tagResource-Score:
				adjacencyMatrix.set(artificialPos, resourcePos, tagResourceScore);
				adjacencyMatrix.set(resourcePos, artificialPos, tagResourceScore);
				
				//set userResource-Score:
				adjacencyMatrix.set(userPos, resourcePos, userResourceScore);
				adjacencyMatrix.set(resourcePos, userPos, userResourceScore);
			}
		} catch (SQLException e1) {
			e1.printStackTrace();
			logger.info("Panic! Filling matrix with artificial TxGy Tags failed.", e1);
		}
	}
	
	/**
	 * Constructs an artificial tag that consists of:
	 * <pre>
	 *  T[id of tag]G[id of grou]
	 *  Example: T12G3
	 * </pre>
	 * 
	 * @param tagId id of a tag
	 * @param groupId id of a group
	 * @return an artificial tag a la "TxGy"
	 */
	public static String constructTxGyTag(int tagId, int groupId){
		if(groupId != TAS.ID_NOT_SET && groupId != 0){
			return ARTIFICIAL_TAG_SUFFIX + tagId + ARTIFICIAL_GROUP_TAG_SUFFIX + groupId;
		}else{
			return  ARTIFICIAL_TAG_SUFFIX + tagId + ARTIFICIAL_GROUP_TAG_SUFFIX + GROUP_NULL_INDICATOR;
		}
	}
}
